package appTool

import (
	"context"
	"crypto/sha256"
	"fmt"
	"github.com/smallnest/rpcx/client"
	"github.com/smallnest/rpcx/share"
	"net/url"
	"strconv"
	"time"
)

type ServerMode string //服务器模式

const (
	Normal  ServerMode = "normal"  //常规
	Backup  ServerMode = "backup"  //备用
	Develop ServerMode = "develop" //开发中的
)

const serverModeMetaKey = "serverMode"

// rpcxServerModeSelect 根据选择服务器模式选择
type rpcxServerModeSelect struct {
	normal     *weightedRoundRobinSelector
	backup     *weightedRoundRobinSelector
	develop    *weightedRoundRobinSelector
	servers    string //服务器hash内容
	ServerMode ServerMode
}

func NewRpcServerModeSelect(mode ServerMode) client.Selector {
	return &rpcxServerModeSelect{
		ServerMode: mode,
	}
}

func (r *rpcxServerModeSelect) Select(ctx context.Context, servicePath, serviceMethod string, args interface{}) string {
	sel, smode := r.selectContext(ctx)
	// 开发环境只取开发环境数据
	if smode == Develop {
		server, _ := sel.getServer(r.develop, Develop)
		//log.Service.WithField("info", r.develop.servers).Debugf("develop选出rpc服务器:%s - %s", server, servicePath)
		return server
	} else if smode == Backup { //备用环境优先取备用服务器,备用数据不存在取常规服务器
		server, notEnd := sel.getServer(r.backup, Backup)
		//log.Service.WithField("info", r.backup.servers).Debugf("backup选出rpc服务器:%s - %s", server, servicePath)
		if server == "" && !notEnd && r.normal != nil {
			server, _ = sel.getServer(r.normal, Normal)
			//log.Service.WithField("info", r.normal.servers).Debugf("backup-normal选出rpc服务器:%s - %s", server, servicePath)
		}
		return server
	} else { //常规服务器,获取常规请求
		server, notEnd := sel.getServer(r.normal, Normal)
		//log.Service.WithField("info", r.normal.servers).Debugf("normal选出rpc服务器:%s - %s", server, servicePath)
		if server == "" && !notEnd && r.backup != nil {
			server, _ = sel.getServer(r.backup, Backup)
			//log.Service.WithField("info", r.backup.servers).Debugf("normal-backup选出rpc服务器:%s - %s", server, servicePath)
		}
		return server
	}
}

func (r *rpcxServerModeSelect) selectContext(ctx context.Context) (*selectContext, ServerMode) {
	sel, ok := ctx.Value(serverModeMetaKey).(*selectContext)
	if !ok {
		sel = &selectContext{
			failServer: map[ServerMode][]string{},
		}
		if sctx, ok := ctx.(*share.Context); ok {
			sctx.SetValue(serverModeMetaKey, sel)
		}
	}
	smod := r.ServerMode
	modStrKey := ctx.Value(serverModeMetaKey + "Key")
	if modStrKey != nil {
		if modeStr, ok := modStrKey.(string); ok {
			switch ServerMode(modeStr) {
			case Backup:
				smod = Backup
			case Develop:
				smod = Develop
			case Normal:
				smod = Normal
			}
		}
	}
	return sel, smod
}

func (r *rpcxServerModeSelect) UpdateServer(servers map[string]string) {
	data, err := json.Marshal(servers)
	if err == nil {
		timeSpan := []byte(time.Now().Format("2006-01-02-15-04")) //附加时间信息,取保超过一分钟没更新的更新一次服务器列表
		data = append(data, timeSpan...)
		serverHash := fmt.Sprintf("%x", sha256.Sum256(data))
		if r.servers == serverHash { //服务器没变化直接返回
			return
		}
		r.servers = serverHash
	}
	//log.Service.WithField("info", servers).Info("更新服务器")
	normalMap := map[string]string{}
	backup := map[string]string{}
	develop := map[string]string{}
	for k, metadata := range servers {
		if v, err := url.ParseQuery(metadata); err == nil {
			mode := v.Get(serverModeMetaKey)
			switch mode {
			case string(Backup):
				backup[k] = metadata
			case string(Develop):
				develop[k] = metadata
			case "all":
				backup[k] = metadata
				develop[k] = metadata
				normalMap[k] = metadata
			default:
				normalMap[k] = metadata
			}
		} else { // 正常情况下全部加入普通服务器
			normalMap[k] = metadata
		}
	}
	if r.normal == nil {
		r.normal = newWeightedRoundRobinSelector(normalMap)
	} else {
		r.normal.UpdateServer(normalMap)
	}
	if r.develop == nil {
		r.develop = newWeightedRoundRobinSelector(develop)
	} else {
		r.develop.UpdateServer(develop)
	}
	if r.backup == nil {
		r.backup = newWeightedRoundRobinSelector(backup)
	} else {
		r.backup.UpdateServer(backup)
	}
}

// weighted is a wrapped server with  weight
type weighted struct {
	Server          string
	Weight          int
	CurrentWeight   int
	EffectiveWeight int
}

// https://github.com/phusion/nginx/commit/27e94984486058d73157038f7950a0a36ecc6e35
func nextWeighted(servers []*weighted) (best *weighted) {
	total := 0

	for i := 0; i < len(servers); i++ {
		w := servers[i]

		if w == nil {
			continue
		}
		//if w is down, continue

		w.CurrentWeight += w.EffectiveWeight
		total += w.EffectiveWeight

		if best == nil || w.CurrentWeight > best.CurrentWeight {
			best = w
		}

	}

	if best == nil {
		return nil
	}

	best.CurrentWeight -= total
	return best
}

// weightedRoundRobinSelector selects servers with weighted.
type weightedRoundRobinSelector struct {
	servers []*weighted
}

func newWeightedRoundRobinSelector(servers map[string]string) *weightedRoundRobinSelector {
	ss := createWeighted(servers)
	return &weightedRoundRobinSelector{servers: ss}
}

func (s *weightedRoundRobinSelector) Select() string {
	ss := s.servers
	if len(ss) == 0 {
		return ""
	}
	w := nextWeighted(ss)
	if w == nil {
		return ""
	}
	return w.Server
}

func (s *weightedRoundRobinSelector) UpdateServer(servers map[string]string) {
	ss := createWeighted(servers)
	s.servers = ss
}

func createWeighted(servers map[string]string) []*weighted {
	ss := make([]*weighted, 0, len(servers))
	for k, metadata := range servers {
		w := &weighted{Server: k, Weight: 1, EffectiveWeight: 1}
		if v, err := url.ParseQuery(metadata); err == nil {
			ww := v.Get("weight")
			if ww != "" {
				if weight, err := strconv.Atoi(ww); err == nil {
					w.Weight = weight
					w.EffectiveWeight = weight
				}
			}
		}
		ss = append(ss, w)
	}
	return ss
}

type selectContext struct {
	failServer map[ServerMode][]string
}

// 校验服务器是否可用,返回(是否可用,对应模式是否还有服务器可用)
func (s *selectContext) getServer(selector *weightedRoundRobinSelector, mode ServerMode) (string, bool) {
	if selector != nil {
		failServer := s.failServer[mode]
		if len(failServer) >= len(selector.servers) { //表示该模式下所有服务器都已经测试过了
			return "", false
		}
		for j := 0; j < 100; j++ { //如果经过100次选择还没选出新服务器，默认该模式没有新服务器可用
			server := selector.Select()
			isNew := true
			for _, v := range failServer {
				if v == server {
					isNew = false
					break
				}
			}
			if isNew {
				s.failServer[mode] = append(s.failServer[mode], server)
				return server, true
			}
		}
	}
	return "", false
}
